﻿using Signit.Sdk.Extentions;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using Viyi.Util;
using Viyi.Util.Codec;
using Viyi.Util.Linq;

namespace Signit.Sdk.Util
{
    public class HmacSignatureBuilder
    {
        public enum BuilderMode
        {
            /// <summary>
            /// 所有字段均加入运算
            /// </summary>
            FULL,

            /// <summary>
            /// 只有请求头字段加入运算
            /// </summary>
            ONLY_HEADER
        }

        public const string DEFAULT_ALGORITHM = "HmacSHA512";
        public const string DEFAULT_CHARSET = "UTF-8";
        public static readonly byte[] DEFAULT_DELIMITER = "\n".ToByteArray(DEFAULT_CHARSET);
        private byte[] delimiter = DEFAULT_DELIMITER;
        private string charset = DEFAULT_CHARSET;
        private string algorithm = DEFAULT_ALGORITHM;
        private string scheme;
        private string host;
        private string method;
        private string resource;
        private string nonce;
        private string apiKey;
        private byte[] apiSecret;
        private byte[] payload;
        private string date;
        private string contentType;

        /**
         * 设置加入运算的相关字符的字符集编码规则.
         *
         * @param charset
         *            字符集编码规则
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder Charset(String charset)
        {
            this.charset = charset;
            return this;
        }

        /**
         * 设置加入运算的字符间的分隔符.
         *
         * @param delimiter
         *            字符间的分隔符
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder Delimiter(byte[] delimiter)
        {
            this.delimiter = delimiter;
            return this;
        }

        public HmacSignatureBuilder Delimiter(byte delimiter)
        {
            this.delimiter = new[] { delimiter };
            return this;
        }

        /**
         * 设置HMAC的算法.
         *
         * @param algorithm
         *            HMAC的算法
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder Algorithm(String algorithm)
        {
            this.algorithm = algorithm;
            return this;
        }

        /**
         * 设置需要加入运算的协议名称.
         *
         * @param scheme
         *            协议名称（如: http/https/ftp/...）
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder Scheme(String scheme)
        {
            this.scheme = scheme;
            return this;
        }

        /**
         * 设置需要加入运算的主机名称.
         *
         * @param host
         *            主机名称（如: signit.cn/10.10.10.10/...）
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder Host(String host)
        {
            this.host = host;
            return this;
        }

        /**
         * 设置需要加入运算的服务器授权给客户端的唯一公开标识.（与<code>apiSecret</code>配套，允许公开）.
         *
         * @param apiKey
         *            服务器授权给客户端的唯一公开标识.（与<code>apiSecret</code>配套，允许公开）
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder ApiKey(String apiKey)
        {
            this.apiKey = apiKey;
            return this;
        }

        /**
         * 设置需要加入运算的请求方法.
         *
         * @param method
         *            请求方法（如: GET/POST/PUT/DELETE/...）
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder Method(String method)
        {
            this.method = method;
            return this;
        }

        /**
         * 设置需要加入运算的请求资源地址（URI）.
         *
         * @param resource
         *            请求资源地址（URI）（如: '/v1/users','/v1/users/123',...）
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder Resource(String resource)
        {
            this.resource = resource;
            return this;
        }

        /**
         * 设置需要加入运算的请求内容类型（Content-Type）.
         *
         * @param contentType
         *            请求内容类型（Content-Type）（如: 'application/json','image/jpeg',...）
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder ContentType(String contentType)
        {
            this.contentType = contentType;
            return this;
        }

        /**
         * 设置需要加入运算的请求时间（Date）.
         *
         * @param date
         *            请求时间（Date）（如: 'Sat, 02 Jul 2011 20:45:57 GMT','Wed, 02 Nov
         *            2016 03:25:54 GMT',...）
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder Date(String date)
        {
            this.date = date;
            return this;
        }

        /**
         * 设置需要加入运算的随机数.
         *
         * @param nonce
         *            随机数（如: '6m0S4nyH1dg7K2gh','8NQq53cLR4g5Y52I',...）
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder Nonce(String nonce)
        {
            this.nonce = nonce;
            return this;
        }

        /**
         * 设置需要加入运算的服务器授权给客户端的私密密钥.（与<code>apiKey</code>配套，但不公开）.
         *

         * @param apiSecret

         *            服务器授权给客户端的私密密钥.（与<code>apiKey</code>配套，但不公开）
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder ApiSecret(byte[] apiSecret)
        {
            this.apiSecret = apiSecret;
            return this;
        }

        /**
         * 设置需要加入运算的请求数据.
         *

         * @param payload

         *            请求数据
         * @return 当前HMAC建造器对象
         * @author zhd
         * @since 1.0.0
         */
        public HmacSignatureBuilder Payload(byte[] payload)
        {
            this.payload = payload;
            return this;
        }

        public string BuildAsHex(BuilderMode builderMode = BuilderMode.FULL)
        {
            return Build(builderMode).HexEncode();
        }

        public string BuildAsBase64(BuilderMode builderMode = BuilderMode.FULL)
        {
            return Build(builderMode).Base64Encode();
        }

        /**
         * 完成HMAC认证消息的构建,并获得签名摘要值.
         *
         * @param builderMode
         *            构建模式的枚举
         * @return HMAC原始签名摘要值的内存数据字节.若构建失败，则返回<code>null</code>
         * @author zhd
         * @since 1.0.0
         */
        public byte[] Build(BuilderMode builderMode = BuilderMode.FULL)
        {
            Func<Stream> builder = null;
            switch (builderMode)
            {
                case BuilderMode.FULL:
                    builder = GetFullStream;
                    break;
                case BuilderMode.ONLY_HEADER:
                    builder = GetOnlyHeaderStream;
                    break;
            }

            if (builder == null)
            {
                Debug.WriteLine($"不支持的参数类型: {builderMode}");
                return null;
            }

            var hmac = HMAC.Create(algorithm);
            hmac.Key = apiSecret;
            return hmac.ComputeHash(builder());
        }


        // 完整的数据运算
        private Stream GetFullStream()
        {
            MemoryStream stream = new MemoryStream();
            SignitStreamWriter writer = new SignitStreamWriter(stream)
                .SetDelimiter(delimiter)
                .SetEncoding(charset);

            new[] { apiKey, contentType, date, host, method, nonce }
                .ForEach(writer.Write);

            if (payload?.Length > 0)
            {
                writer.Write(payload);
            }

            new[] { resource, scheme }
                .ForEach(writer.Write);

            stream.Seek(0, SeekOrigin.Begin);
            return stream;
        }

        private Stream GetOnlyHeaderStream()
        {
            MemoryStream stream = new MemoryStream();
            SignitStreamWriter writer = new SignitStreamWriter(stream)
                .SetDelimiter(delimiter)
                .SetEncoding(charset);

            new[] { apiKey, contentType, date, host, method, nonce, resource, scheme }
                .ForEach(writer.Write);

            stream.Seek(0, SeekOrigin.Begin);
            return stream;
        }

        public void VerifySignature(byte[] expect,
            BuilderMode mode = BuilderMode.FULL,
            string errMessage = null)
        {
            if (Enumerable.SequenceEqual(expect, Build(mode)))
            {
                return;
            }

            throw new ArgumentException(errMessage);
        }

        public void VerifyHexSignature(string expect,
            BuilderMode mode = BuilderMode.FULL,
            string errMessage = null)
        {
            if (expect == BuildAsHex(mode))
            {
                return;
            }

            throw new ArgumentException(errMessage);
        }

        public void VerifyBase64Signature(string expect,
            BuilderMode mode = BuilderMode.FULL,
            string errMessage = null)
        {
            if (expect == BuildAsBase64(mode))
            {
                return;
            }

            throw new ArgumentException(errMessage);
        }
    }
}
